﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Runtime.Serialization.Json;
using Fixx.DTO;
using Newtonsoft.Json;
using Fixx.DTO.Parser;

namespace Fixx.Connector
{
	[Serializable]
	public class FixxRepository : IFixxRepository
	{
		public delegate void WorkingHandler(object sender, EventArgs ca);
		[field: NonSerialized]
		public event WorkingHandler OnBeginConnection = delegate { };
		[field: NonSerialized]
		public event WorkingHandler OnEndConnection = delegate { };
		public delegate void CacheStateHandler(object sender, EventArgs ca);

		[field: NonSerialized]
		public event CacheStateHandler OnCacheStateChange = delegate { };


		public IFixxSettings FixxSettings { get; set; }

		//cache
		public FixxUser ActiveUser { get; private set; }
		public IEnumerable<FixxProject> CachedProjects { get; private set; }
		public IEnumerable<FixxUser> CachedUsers { get; private set; }
		public IEnumerable<FixxPriority> CachedPriorities { get; private set; }
		private Dictionary<int, FixxProjectCache> _ProjectCaching { get; set; }


		public FixxRepository()
		{
		}

		public FixxRepository(IFixxSettings fixxSettings)
		{
			//if (fixxSettings == null)
			//hrow ("fixxerror");

			this.FixxSettings = fixxSettings;
		}

		public void ClearCache(FixxRepositoryCache items)
		{
			if ((items & FixxRepositoryCache.Priorities) == FixxRepositoryCache.Priorities)
				this.CachedPriorities = null;
			if ((items & FixxRepositoryCache.ProjectData) == FixxRepositoryCache.ProjectData)
				this._ProjectCaching = null;
			if ((items & FixxRepositoryCache.ProjectList) == FixxRepositoryCache.ProjectList)
				this.CachedProjects = null;
			if ((items & FixxRepositoryCache.Users) == FixxRepositoryCache.Users)
				this.CachedUsers = null;

		}

		public void GetProjectsAsync(Action<IEnumerable<FixxProject>> success, Action<FixxError> failure)
		{
			if (this.CachedProjects != null)
			{
				success(this.CachedProjects);
				return;
			}
			this._BootStrapCache(() =>
			{
				FixxConnection connection = new FixxConnection(this.FixxSettings);

				connection.Get("/projects", (status) =>
				{
					if (status.Success)
					{
						try
						{
							this.CachedProjects = FixxParser.ParseIEnumerable<FixxProject>(status.Result, this.FixxSettings.FixxParserFormat);
							success(this.CachedProjects.ToList());

						}
						catch (Exception e)
						{
							failure(new FixxError()
							{
								Message = e.Message
							});
						}
					}
					else if (failure != null)
						failure(status.Error);
				});
			});
		}

		public void Login(Action<FixxUser> success, Action<FixxError> failure)
		{
			FixxConnection connection = new FixxConnection(this.FixxSettings);

			connection.Get("/users/" + this.FixxSettings.Username, (status) =>
			{
				if (status.Success)
				{
					var user = FixxParser.Parse<FixxUser>(status.Result, this.FixxSettings.FixxParserFormat);
					success(user);
				}
				else if (failure != null)
					failure(status.Error);
			});
		}

		#region GetIssuesAsync
		
			
		public void GetIssuesAsync(FixxIssueFilter filter, Action<IEnumerable<FixxIssue>> success, Action<FixxError> failure)
		{
			this.GetIssuesAsync(filter, (e, i) => success(e), failure);
		}
		public void GetIssuesAsync(int projectId, Action<IEnumerable<FixxIssue>> success, Action<FixxError> failure)
		{
			this.GetIssuesAsync(new FixxIssueFilter() { ProjectId = projectId }, success, failure);
		}
		public void GetIssuesAsync(int projectId, Action<IEnumerable<FixxIssue>, int> success, Action<FixxError> failure)
		{
			this.GetIssuesAsync(new FixxIssueFilter() { ProjectId = projectId }, success, failure);
		}
		public void GetIssuesAsync(
			FixxIssueFilter filter,
			Action<IEnumerable<FixxIssue>, int> success,
			Action<FixxError> failure
			)
		{
			if (filter.ProjectId <= 0)
				throw new ArgumentOutOfRangeException();

			this.BootStrapProjectCache(filter.ProjectId, () =>
			{
				FixxConnection connection = new FixxConnection(this.FixxSettings);

				connection.Get("/projects/" + filter.ProjectId + "/issues", (status) =>
				{
					if (status.Success)
					{
						var pc = this._ProjectCaching[filter.ProjectId];

						Func<XElement, bool> whereFilter = (element) =>
						{
							bool include = false;
							include =
								(filter.IncludeClosed
									|| !filter.IncludeClosed && element.Element("closed").Value == "false"
								)
								&&
								(filter.IncludeResolved
									|| !filter.IncludeResolved && element.Element("resolution") == null
								)
								&&
								(!filter.OnlyAssignedToMe
									|| (filter.OnlyAssignedToMe
										&& (element.Element("assignedTo") != null
											&& element.Element("assignedTo").Value == this.ActiveUser.Id.ToString())));
							if (!string.IsNullOrEmpty(filter.Query))
							{
								var q = filter.Query.ToLower();
								var te = element.Element("title");
								var de = element.Element("description");
								include = (te != null && te.Value.ToLower().Contains(q))
										|| (de != null && de.Value.ToLower().Contains(q));
							}
							return include;


						};

						var issueElements = XDocument.Parse(status.Result).Descendants("issue");

						pc.Issues = FixxParser.ParseIEnumerable<FixxIssue>(status.Result, this.FixxSettings.FixxParserFormat); ;

						success(pc.Issues.ToList(), issueElements.Count());
					}
					else if (failure != null)
						failure(status.Error);
				});
			}
			);
		}

		#endregion

		public FixxProjectCache GetProjectCache(int projectId)
		{
			if (this._ProjectCaching != null
				&& this._ProjectCaching.ContainsKey(projectId))
				return this._ProjectCaching[projectId];
			else
				return null;
		}

		public void GetCommentsAsync(int projectId, int issueId, Action<IEnumerable<FixxComment>> success, Action<FixxError> failure)
		{
			if (issueId <= 0)
				throw new ArgumentOutOfRangeException();

			this.BootStrapProjectCache(projectId, () =>
			{
				FixxConnection connection = new FixxConnection(this.FixxSettings);

				connection.Get("/issues/" + issueId + "/comments", (status) =>
				{
					if (status.Success)
					{
						if (this.FixxSettings.TransportLayer == TransportLayer.XML)
						{
							var pc = this._ProjectCaching[projectId];

							var comments = FixxParser.ParseIEnumerable<FixxComment>(status.Result, this.FixxSettings.FixxParserFormat);
							success(comments.ToList());
						}
						else if (this.FixxSettings.TransportLayer == TransportLayer.JSON)
						{
							List<FixxComment> comments = JsonConvert.DeserializeObject<List<FixxComment>>(status.Result);
							
							//var pc = this._ProjectCaching[projectId];
							//DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(IEnumerable<FixxComment>)); 
							//var comments = DataCon
						}
					}
					else if (failure != null)
						failure(status.Error);
				});
			}
			);
		}
		public void PostCommentAsync(int issueId, FixxComment comment, Action<FixxComment> success, Action<FixxError> failure)
		{
			if (issueId <= 0)
				throw new ArgumentOutOfRangeException();
			this._BootStrapCache(() =>
			{
				FixxConnection connection = new FixxConnection(this.FixxSettings);

				connection.Post("/issues/" + issueId + "/comments", comment, (status) =>
				{
					if (status.Success)
					{
						var c =
							FixxParser.Parse<FixxComment>(status.Result, this.FixxSettings.FixxParserFormat); ;
						success(c);
					}
					else if (failure != null)
						failure(status.Error);
				});
			}
			);

		}
	/*
		public void PostIssue(int projectId, FixxIssue issue, Action<FixxIssue> success, Action<FixxError> failure)
		{
			if (issue == null)
				throw new ArgumentNullException();
			if (projectId == null)
				throw new ArgumentNullException();

			if (issue.Area == null)
				failure(new FixxError() { Type = FixxErrorType.Uncaught, Message = "Area is null" });
			else if (string.IsNullOrEmpty(issue.Title))
				failure(new FixxError() { Type = FixxErrorType.Uncaught, Message = "Title is null or empty" });
			else if (string.IsNullOrEmpty(issue.Description))
				failure(new FixxError() { Type = FixxErrorType.Uncaught, Message = "Description is null or empty" });
			else
			{
				this.BootStrapProjectCache(projectId, () =>
				{
					FixxConnection connection = new FixxConnection(this.FixxSettings);
					var pc = this._ProjectCaching[projectId];

					Action<FixxStatus> cb = (status) =>
						{
							if (status.Success)
							{
								 
								FixxIssue i = new FixxIssue
								(
									XDocument.Parse(status.Result).Root,
									this.CachedUsers, 
									pc.IssueTypes, 
									this.CachedPriorities, 
									pc.Areas, 
									pc.Versions, 
									pc.Resolutions
								);
								success(i);
							}
							else if (failure != null)
							{
							}
								
						};

					if (issue.Id > 0)
					{
						connection.Put("/issues/"+issue.Id.ToString(), issue, cb);
					}
					else
					{
						connection.Post("/issues", issue, cb);
					}
				});
			}
		}
*/
		public void GetIssueAsync(int projectId, int issueId, Action<FixxIssue> success, Action<FixxError> failure)
		{
			if (issueId <= 0)
				throw new ArgumentOutOfRangeException();

			this.BootStrapProjectCache(projectId, () =>
			{
				FixxConnection connection = new FixxConnection(this.FixxSettings);

				connection.Get("/issues/" + issueId, (status) =>
				{
					if (status.Success)
					{
						var pc = this._ProjectCaching[projectId];

						var e = XDocument.Parse(status.Result).Root;

						var issue = FixxParser.Parse<FixxIssue>(status.Result, this.FixxSettings.FixxParserFormat);

						success(issue);
					}
					else if (failure != null)
						failure(status.Error);
				});
			}
			);
		}
		
		private void _BootStrapCache(Action doneCaching)
		{
			Action wrapDoneCaching = () =>
				{
					this.OnCacheStateChange(this, EventArgs.Empty);
					doneCaching();
				};
			
			FixxMultiConnection transaction = new FixxMultiConnection(this.FixxSettings);
			if (this.ActiveUser == null)
				transaction.AppendGetConnectionTo("/users/" + this.FixxSettings.Username, (result) =>
				{
					this.ActiveUser = FixxParser.Parse<FixxUser>(result, this.FixxSettings.FixxParserFormat);
				});
			if (this.CachedPriorities == null)
				transaction.AppendGetConnectionTo("/priorities", (result) =>
				{
					this.CachedPriorities = FixxParser.ParseIEnumerable<FixxPriority>(result, this.FixxSettings.FixxParserFormat).ToList();
				});
			if (this.CachedUsers == null)
				transaction.AppendGetConnectionTo("/users", (result) =>
				{
					this.CachedUsers = FixxParser.ParseIEnumerable<FixxUser>(result, this.FixxSettings.FixxParserFormat).ToList();
				});
			if (transaction.HasPendingConnections())
				transaction.Begin(wrapDoneCaching, wrapDoneCaching);
			else
				doneCaching();
		}
		public void BootStrapProjectCache(int projectId, Action doneCaching)
		{
			Action wrapDoneCaching = () =>
			{
				this.OnCacheStateChange(this, EventArgs.Empty);
				doneCaching();
			};

			if (this._ProjectCaching == null)
				this._ProjectCaching = new Dictionary<int, FixxProjectCache>();

			if (!this._ProjectCaching.ContainsKey(projectId))
				this._ProjectCaching.Add(projectId, new FixxProjectCache());


			FixxMultiConnection transaction = new FixxMultiConnection(this.FixxSettings);
			if (this.CachedPriorities == null)
				transaction.AppendGetConnectionTo("/priorities", (result) =>
				{
					this.CachedPriorities = FixxParser.ParseIEnumerable<FixxPriority>(result, this.FixxSettings.FixxParserFormat);
				});
			if (this.CachedUsers == null)
				transaction.AppendGetConnectionTo("/users", (result) =>
				{
					this.CachedUsers = FixxParser.ParseIEnumerable<FixxUser>(result, this.FixxSettings.FixxParserFormat);
				});
			if (this._ProjectCaching[projectId].IssueTypes == null)
				transaction.AppendGetConnectionTo("/projects/" + projectId + "/issue-types", (result) =>
				{
					this._ProjectCaching[projectId].IssueTypes = FixxParser.ParseIEnumerable<FixxIssueType>(result, this.FixxSettings.FixxParserFormat);
				});
			if (this._ProjectCaching[projectId].Resolutions == null)
				transaction.AppendGetConnectionTo("/projects/" + projectId + "/resolutions", (result) =>
				{
					this._ProjectCaching[projectId].Resolutions = FixxParser.ParseIEnumerable<FixxResolution>(result, this.FixxSettings.FixxParserFormat);
				});
			if (this._ProjectCaching[projectId].Areas == null)
				transaction.AppendGetConnectionTo("/projects/" + projectId + "/areas", (result) =>
				{
					this._ProjectCaching[projectId].Areas = FixxParser.ParseIEnumerable<FixxArea>(result, this.FixxSettings.FixxParserFormat);
				});
			if (this._ProjectCaching[projectId].Versions == null)
				transaction.AppendGetConnectionTo("/projects/" + projectId + "/versions", (result) =>
				{
					this._ProjectCaching[projectId].Versions = FixxParser.ParseIEnumerable<FixxVersion>(result, this.FixxSettings.FixxParserFormat);
				});
			if (transaction.HasPendingConnections())
				transaction.Begin(wrapDoneCaching, wrapDoneCaching);
			else
				doneCaching();
		}

	}
}
